// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Locker.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework
{
    #region Using Directives

    using System;
    using System.Threading;

    using Org.OpenTrader.Framework.Interfaces;

    #endregion

    /// <summary>
    /// The locker.
    /// </summary>
    public class Locker : ILocker
    {
        #region Constants and Fields

        /// <summary>
        /// Lock for static mutable properties.
        /// </summary>
        private static readonly object staticLock = new object();

        /// <summary>
        /// The default timeout.
        /// </summary>
        private readonly int defaultTimeout;

        /// <summary>
        /// The monitor.
        /// </summary>
        private readonly object monitor = new object();

        /// <summary>
        /// The default timeout for new instances of this class
        /// where the default timeout isn't otherwise specified.
        /// Defaults to Timeout.Infinite.
        /// </summary>
        private static int defaultDefaultTimeout = Timeout.Infinite;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Locker"/> class.
        /// </summary>
        public Locker()
        {
            this.defaultTimeout = Timeout.Infinite;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Locker"/> class.
        /// </summary>
        /// <param name="defaultTimeout">
        /// The default timeout.
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// </exception>
        public Locker(int defaultTimeout)
        {
            if (defaultTimeout < Timeout.Infinite)
            {
                throw new ArgumentOutOfRangeException("Invalid timeout specified");
            }

            this.defaultTimeout = defaultTimeout;
        }

        #endregion

        #region Properties

        /// <summary>
        /// The default timeout for the 
        /// </summary>
        public int DefaultTimeout
        {
            get
            {
                return this.defaultTimeout;
            }
        }

        /// <summary>
        /// The internal monitor used for Locker. While this
        /// is owned by the thread, it can be used for waiting
        /// and pulsing in the usual way. Note that manually entering/exiting
        /// this monitor could result in the lock malfunctioning.
        /// </summary>
        public object Monitor
        {
            get
            {
                return this.monitor;
            }
        }

        /// <summary>
        /// Gets or sets DefaultDefaultTimeout.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">
        /// </exception>
        private static int DefaultDefaultTimeout
        {
            get
            {
                lock (staticLock)
                {
                    return defaultDefaultTimeout;
                }
            }

            set
            {
                if (value < Timeout.Infinite)
                {
                    throw new ArgumentOutOfRangeException("Invalid timeout specified");
                }

                lock (staticLock)
                {
                    defaultDefaultTimeout = value;
                }
            }
        }

        #endregion

        #region Implemented Interfaces

        #region ILocker

        /// <summary>
        /// The lock.
        /// </summary>
        /// <returns>
        /// </returns>
        public ILockerToken Lock()
        {
            return this.Lock(this.defaultTimeout);
        }

        /// <summary>
        /// The lock.
        /// </summary>
        /// <param name="timeout">
        /// The timeout.
        /// </param>
        /// <returns>
        /// </returns>
        /// <exception cref="ArgumentOutOfRangeException">
        /// </exception>
        /// <exception cref="LockerTimeoutException">
        /// </exception>
        public ILockerToken Lock(int timeout)
        {
            if (timeout < Timeout.Infinite)
            {
                throw new ArgumentOutOfRangeException("Invalid timeout specified");
            }

            if (!System.Threading.Monitor.TryEnter(this.monitor, timeout))
            {
                throw new LockerTimeoutException("Failed to acquire lock");
            }

            return new LockerToken(this);
        }

        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// The unlock.
        /// </summary>
        private void Unlock()
        {
            System.Threading.Monitor.Exit(this.monitor);
        }

        #endregion

        /// <summary>
        /// The locker token.
        /// </summary>
        private class LockerToken : ILockerToken, IDisposable
        {
            #region Constants and Fields

            /// <summary>
            /// The parent.
            /// </summary>
            private Locker parent;

            #endregion

            #region Constructors and Destructors

            /// <summary>
            /// Initializes a new instance of the <see cref="LockerToken"/> class.
            /// </summary>
            /// <param name="parent">
            /// The parent.
            /// </param>
            internal LockerToken(Locker parent)
            {
                this.parent = parent;
            }

            #endregion

            #region Implemented Interfaces

            #region IDisposable

            /// <summary>
            /// The dispose.
            /// </summary>
            public void Dispose()
            {
                if (this.parent == null)
                {
                    return;
                }

                this.parent.Unlock();
                this.parent = null;
            }

            #endregion

            #endregion
        }
    }
}